﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;

namespace LinqToLfsWorld.Queries
{

	/// <summary>
	/// Handles the making, and caching of, requests to LfsWorld.
	/// </summary>
	internal class LfsWorldRequest
	{

		#region Singleton Implementation

		/// <summary>
		/// Initializes a new instance of the <see cref="LfsWorldRequest"/> class.
		/// </summary>
		private LfsWorldRequest()
		{
			RequestCacheKey = null;
			CacheTime = TimeSpan.FromSeconds(5.5);
			GlobalCacheTime = TimeSpan.FromSeconds(5.1);
			EnableRequestKerbing = true;
		}

		private static object _objLock = new object();
		private static LfsWorldRequest _Instance = default(LfsWorldRequest);

		public static LfsWorldRequest Instance
		{
			get
			{
				if (_Instance == null)
				{
					lock (_objLock)
					{
						if (_Instance == null)
							_Instance = new LfsWorldRequest();
					}
				}
				return _Instance;
			}
		}

		#endregion


		#region Events 

		internal event LfsRequestHandler RequestMade;

		#endregion


		#region Cache Members

		/// <summary>
		/// Gets the caching controller.
		/// </summary>
		internal Cache Cache
		{
			get { return HttpRuntime.Cache; }
		}

		/// <summary>
		/// Gets or sets the ability to use in-memory caching of results.
		/// </summary>
		public bool CachingEnabled
		{
			get { return _CachingEnabled; }
			set { _CachingEnabled = value; }
		}
		private bool _CachingEnabled = true;

		/// <summary>
		/// The key to use when caching pubstat responses
		/// </summary>
		internal string RequestCacheKey { get; set; }
		private object _requestCacheObj = new object();

		/// <summary>
		/// Gets whether or not caching is available on the host machine.
		/// </summary>
		public bool CachingAvailable
		{
			get
			{
				return ((Cache != null) && (CachingEnabled));
			}
		}

		/// <summary>
		/// If true, *any* request is blocked if it is made before an alloted time period is up.
		/// This is enabled by default.
		/// </summary>
		public bool EnableRequestKerbing { get; set; }

		/// <summary>
		/// Gets or sets the time the results should be cached for.
		/// </summary>
		public TimeSpan CacheTime { get; set; }

		// Can only make *any* request every so often
		private  TimeSpan GlobalCacheTime { get; set; }

		/// <summary>
		/// Returns true if the request manager can make a request.
		/// </summary>
		public bool CanMakeRequest
		{
			get
			{
				return ((Cache[this.RequestCacheKey.ToString()] == null) || (EnableRequestKerbing == false));
			}
		}

		/// <summary>
		/// Helper method for inserting an item into the cache.
		/// </summary>
		/// <param name="key">The cache item key</param>
		/// <param name="item">The item to cache</param>
		/// <param name="cacheTime">The time to cache the item for</param>
		internal void InsertCacheItem(string key, object item, TimeSpan cacheTime)
		{
			if (CachingAvailable)
			{
				Cache.Add(key, item, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
					cacheTime, CacheItemPriority.Normal, null);
			}
		}

		#endregion


		/// <summary>
		/// Makes a request to the pubstats server and (if applicable) caches it for later.
		/// </summary>
		/// <param name="url">The url to request.</param>
		public LfsWorldResponse MakeRequest(string url)
		{
			ValidateCacheKey(this.RequestCacheKey);

			LfsWorldResponse response = new LfsWorldResponse() { RequestUrl = url, FromCache = false };

			if (RequiresNewRequest(url))
			{
				// Check if we *can* make one
				if (CanMakeRequest)
				{
					response.ResponseData = LfsHelper.GetPubstatResponse(url);

					InsertCacheItem(url, response.ResponseData, this.CacheTime);

					if (EnableRequestKerbing)
					{
						// TODO: Modify this to use the identkey or the username + password as the cache key.

						// Control the request kerbing using the cache.
						InsertCacheItem(RequestCacheKey.ToString(), _requestCacheObj, GlobalCacheTime);
					}

				}
				else
				{
					throw new PubstatRequestException(url, null, "Unable to make the request at this time. Wait 5 seconds and try again.");
				}
			}
			else
			{
				response.FromCache = true;
				response.ResponseData = Cache[url] as string;
			}

			if (RequestMade != null)
				RequestMade(this, response);

			return response;
		}

		/// <summary>
		/// Returns true if we need to make another physical request for the supplied url.
		/// </summary>
		public bool RequiresNewRequest(string url)
		{
			if (this.CachingAvailable == false)
				return true;

			return (Cache[url] == null);
		}

		/// <summary>
		/// Throws an exception if the cache key is not nothing or empty
		/// </summary>
		/// <param name="cacheKey">The cache key to validate</param>
		private void ValidateCacheKey(string cacheKey)
		{
			if (string.IsNullOrEmpty(cacheKey))
				throw new ArgumentNullException("The request cache key is invalid. Please set this before making your first request.");
		}

	}
}
